#!/usr/bin/env ruby
module Setup
  VERSION   = '5.0.0'
end
class << File #:nodoc: all
  unless respond_to?(:read)   # Ruby 1.6 and less
    def read(fname)
      open(fname){ |f| return f.read }
    end
  end
  def dir?(path)
    directory?((path[-1,1] == '/') ? path : path + '/')
  end
end
unless Errno.const_defined?(:ENOTEMPTY)   # Windows?
  module Errno  #:nodoc:
    class ENOTEMPTY  #:nodoc:
    end
  end
end
module Setup
  class Project
    ROOT_MARKER = '{setup.rb,script/setup,meta/,MANIFEST,lib/}'
    def rootdir
      @rootdir ||= (
        root = Dir[File.join(Dir.pwd, ROOT_MARKER)].first
        if !root
          raise Error, "not a project directory"
        else
          Dir.pwd
        end
      )
    end
    def name
      @name = (
        if file = Dir["{script/setup,meta,.meta}/name"].first
          File.read(file).strip
        else
          nil
        end
      )
    end
    def loadpath
      @loadpath ||= (
        if file = Dir.glob('{script/setup,meta,.meta}/loadpath').first
          raw = File.read(file).strip.chomp(']')
          raw.split(/[\n,]/).map do |e|
            e.strip.sub(/^[\[-]\s*/,'')
          end
        else
          nil
        end
      )
    end
    def extconfs
      @extconfs ||= Dir['ext/**/extconf.rb']
    end
    def extensions
      @extensions ||= extconfs.collect{ |f| File.dirname(f) }
    end
    def compiles?
      !extensions.empty?
    end
  end
end
module Setup
  class Session
    attr :options
    def initialize(options={})
      @options = options
      self.io ||= StringIO.new  # log instead ?
    end
    def io
      @options[:io]
    end
    def io=(anyio)
      @options[:io] = anyio
    end
    def trace?; @options[:trace]; end
    def trace=(val)
      @options[:trace] = val
    end
    def trial?; @options[:trial]; end
    def trial=(val)
      @options[:trial] = val
    end
    def quiet?; @options[:quiet]; end
    def quiet=(val)
      @options[:quiet] = val
    end
    def all
      log_header('config')
      config
      if configuration.compile? && project.compiles?
        log_header('setup')
        setup
      end
      if configuration.test?
        log_header('test')
        test
      end
      log_header('install')
      install
      if configuration.document?
        log_header('document')
        document
      end
    end
    def config
      if configuration.save_config
        io.puts "Configuration saved." unless quiet?
      else
        io.puts "Configuration current." unless quiet?
      end
      puts configuration if trace? && !quiet?
      compiler.configure
    end
    def setup
      abort "must setup config first" unless configuration.exist?
      compiler.compile
    end
    def install
      abort "must setup config first" unless configuration.exist?
      installer.install
    end
    def test
      return unless tester.testable?
      tester.test
    end
    def document
      documentor.document
    end
    def clean
      compiler.clean
    end
    def distclean
      compiler.distclean
    end
    def uninstall
      uninstaller.uninstall
    end
    def show
      puts configuration
    end
    def project
      @project ||= Project.new
    end
    def configuration
      @configuration ||= Configuration.new
    end
    def compiler
      @compiler ||= Compiler.new(project, configuration, options)
    end
    def installer
      @installer ||= Installer.new(project, configuration, options)
    end
    def tester
      @tester ||= Tester.new(project, configuration, options)
    end
    def documentor
      @documentor ||= Documentor.new(project, configuration, options)
    end
    def uninstaller
      @uninstaller ||= Uninstaller.new(project, configuration, options)
    end
    def log_header(phase)
       return if quiet?
       line = '- ' * 4 + ' -' * 28
       line[5,phase.size] = " #{phase.to_s.upcase} "
       io.puts "\n" + line + "\n\n"
    end
  end
end
module Setup
  FILETYPES = %w( bin lib ext data etc man doc )
  INSTALL_RECORD = 'InstalledFiles'
  class Base
    attr :project
    attr :config
    attr_accessor :trial
    attr_accessor :trace
    attr_accessor :quiet
    attr_accessor :io
    def initialize(project, configuration, options={})
      @project = project
      @config  = configuration
      options.each do |k,v|
        __send__("#{k}=", v) if respond_to?("#{k}=")
      end
    end
    def trial? ; @trial ; end
    def trace? ; @trace ; end
    def quiet? ; @quiet ; end
    def rootdir
      project.rootdir
    end
    def bash(*args)
      $stderr.puts args.join(' ') if trace?
      system(*args) or raise RuntimeError, "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
    end
    alias_method :command, :bash
    def ruby(*args)
      bash(config.rubyprog, *args)
    end
    def ask(question, answers=nil)
      $stdout.puts "#{question}"
      $stdout.puts " [#{answers}] " if answers
      until inp = $stdin.gets ; sleep 1 ; end
      inp.strip
    end
    def trace_off #:yield:
      begin
        save, @trace = trace?, false
        yield
      ensure
        @trace = save
      end
    end
    def rm_f(path)
      io.puts "rm -f #{path}" if trace?
      return if trial?
      force_remove_file(path)
    end
    def force_remove_file(path)
      begin
        remove_file(path)
      rescue
      end
    end
    def remove_file(path)
      File.chmod 0777, path
      File.unlink path
    end
    def rmdir(path)
      $stderr.puts "rmdir #{path}" if trace?
      return if trial?
      Dir.rmdir path
    end
  end
  class Error < StandardError
  end
end
module Setup
  class Compiler < Base
    def configure
      extdirs.each do |dir|
        Dir.chdir(dir) do
          ruby("extconf.rb") if File.exist?('extconf.rb')
        end
      end
    end
    def compile
      extdirs.each do |dir|
        Dir.chdir(dir) do
          make
        end
      end
    end
    def clean
      extdirs.each do |dir|
        Dir.chdir(dir) do
          make('clean')
        end
      end
    end
    def distclean
      extdirs.each do |dir|
        Dir.chdir(dir) do
          make('distclean')
        end
      end
    end
    def extdirs
      Dir['ext/**/*/{MANIFEST,extconf.rb}'].map do |f|
        File.dirname(f)
      end.uniq
    end
    def make(task=nil)
      return unless File.exist?('Makefile')
      bash(*[config.makeprog, task].compact)
    end
  end
end
require 'rbconfig'
require 'fileutils'
require 'erb'
require 'yaml'
module Setup
  class Configuration
    RBCONFIG  = ::Config::CONFIG
    CONFIGFILE = 'SetupConfig'
    METACONFIGFILE = 'script/.setup/metaconfig{,.rb}'
    def self.options
      @options ||= []
    end
    def self.option(name, type, description)
      options << [name.to_s, type, description]
      attr_accessor(name)
    end
    option :prefix          , :path, 'path prefix of target environment'
    option :bindir          , :path, 'directory for commands'
    option :libdir          , :path, 'directory for libraries'
    option :datadir         , :path, 'directory for shared data'
    option :mandir          , :path, 'directory for man pages'
    option :docdir          , :path, 'directory for documentation'
    option :rbdir           , :path, 'directory for ruby scripts'
    option :sodir           , :path, 'directory for ruby extentions'
    option :sysconfdir      , :path, 'directory for system configuration files'
    option :localstatedir   , :path, 'directory for local state data'
    option :libruby         , :path, 'directory for ruby libraries'
    option :librubyver      , :path, 'directory for standard ruby libraries'
    option :librubyverarch  , :path, 'directory for standard ruby extensions'
    option :siteruby        , :path, 'directory for version-independent aux ruby libraries'
    option :siterubyver     , :path, 'directory for aux ruby libraries'
    option :siterubyverarch , :path, 'directory for aux ruby binaries'
    option :rubypath        , :prog, 'path to set to #! line'
    option :rubyprog        , :prog, 'ruby program used for installation'
    option :makeprog        , :prog, 'make program to compile ruby extentions'
    option :extconfopt      , :opts, 'options to pass-thru to extconf.rb'
    option :shebang         , :pick, 'shebang line (#!) editing mode (all,ruby,never)'
    option :no_ext          , :bool, 'do not compile/install ruby extentions'
    option :no_test         , :bool, 'do not run tests'
    option :no_doc          , :bool, 'do not generate ri documentation'
    option :install_prefix  , :path, 'install to alternate root location'
    option :root            , :path, 'install to alternate root location'
    option :installdirs     , :pick, 'install location mode (site,std,home)'  #, local)
    option :type            , :pick, 'install location mode (site,std,home)'
    ::Config::CONFIG.each do |key,val|
      next if key == "configure_args"
      name = key.to_s.downcase
      define_method(name){ val }
    end
    ::Config::CONFIG["configure_args"].each do |ent|
      key, val = *ent.split("=")
      name = key.downcase
      name = name.sub(/^--/,'')
      name = name.gsub(/-/,'_')
      define_method(name){ val }
    end
    def options
      self.class.options
    end
    def initialize(values={})
      initialize_metaconfig
      initialize_defaults
      initialize_environment
      initialize_configfile
      values.each{ |k,v| __send__("#{k}=", v) }
      yeild(self) if block_given?
    end
    def initialize_metaconfig
      if File.exist?(METACONFIGFILE)
        script = File.read(METACONFIGFILE)
        (class << self; self; end).class_eval(script)
      end
    end
    def initialize_defaults
      self.type    = 'site'
      self.no_doc  = true
      self.no_test = false
      self.no_ext  = false
    end
    def initialize_environment
      options.each do |name, type, description|
        if value = ENV["RUBYSETUP_#{name.to_s.upcase}"]
          __send__("#{name}=",value)
        end
      end
    end
    def initialize_configfile
      if File.exist?(CONFIGFILE)
        erb = ERB.new(File.read(CONFIGFILE))
        txt = erb.result(binding)
        dat = YAML.load(txt)
        dat.each do |k, v|
          next if 'type' == k
          next if 'installdirs' == k
          k = k.gsub('-','_')
          __send__("#{k}=", v)
        end
        if dat['type']
          self.type = dat['type']
        end
        if dat['installdirs']
          self.installdirs = dat['installdirs']
        end
      end
    end
    def base_bindir
      @base_bindir ||= subprefix('bindir')
    end
    def base_libdir
      @base_libdir ||= subprefix('libdir')
    end
    def base_datadir
      @base_datadir ||= subprefix('datadir')
    end
    def base_mandir
      @base_mandir ||= subprefix('mandir')
    end
    def base_docdir
      @base_docdir || File.dirname(subprefix('docdir'))
    end
    def base_rubylibdir
      @rubylibdir ||= subprefix('rubylibdir')
    end
    def base_rubyarchdir
      @base_rubyarchdir ||= subprefix('archdir')
    end
    def base_sysconfdir
      @base_sysconfdir ||= subprefix('sysconfdir')
    end
    def base_localstatedir
      @base_localstatedir ||= subprefix('localstatedir')
    end
    def type
      @type ||= 'site'
    end
    def type=(val)
      @type = val
      case val.to_s
      when 'std', 'ruby'
        @rbdir = librubyver       #'$librubyver'
        @sodir = librubyverarch   #'$librubyverarch'
      when 'site'
        @rbdir = siterubyver      #'$siterubyver'
        @sodir = siterubyverarch  #'$siterubyverarch'
      when 'home'
        self.prefix = File.join(home, '.local')  # TODO: Use XDG
        @rbdir = nil #'$libdir/ruby'
        @sodir = nil #'$libdir/ruby'
      else
        raise Error, "bad config: use type=(std|site|home) [#{val}]"
      end
    end
    alias_method :installdirs, :type
    alias_method :installdirs=, :type=
    alias_method :install_prefix, :root
    alias_method :install_prefix=, :root=
    def prefix
      @prefix ||= RBCONFIG['prefix']
    end
    def prefix=(path)
      @prefix = pathname(path)
    end
    def libruby
      @libruby ||= RBCONFIG['prefix'] + "/lib/ruby"
    end
    def libruby=(path)
      path = pathname(path)
      @librubyver = librubyver.sub(libruby, path)
      @librubyverarch = librubyverarch.sub(libruby, path)
      @libruby = path
    end
    def librubyver
      @librubyver ||= RBCONFIG['rubylibdir']
    end
    def librubyver=(path)
      @librubyver = pathname(path)
    end
    def librubyverarch
      @librubyverarch ||= RBCONFIG['archdir']
    end
    def librubyverarch=(path)
      @librubyverarch = pathname(path)
    end
    def siteruby
      @siteruby ||= RBCONFIG['sitedir']
    end
    def siteruby=(path)
      path = pathname(path)
      @siterubyver = siterubyver.sub(siteruby, path)
      @siterubyverarch = siterubyverarch.sub(siteruby, path)
      @siteruby = path
    end
    def siterubyver
      @siterubyver ||= RBCONFIG['sitelibdir']
    end
    def siterubyver=(path)
      @siterubyver = pathname(path)
    end
    def siterubyverarch
      @siterubyverarch ||= RBCONFIG['sitearchdir']
    end
    def siterubyverarch=(path)
      @siterubyverarch = pathname(path)
    end
    def bindir
      @bindir || File.join(prefix, base_bindir)
    end
    def bindir=(path)
      @bindir = pathname(path)
    end
    def libdir
      @libdir || File.join(prefix, base_libdir)
    end
    def libdir=(path)
      @libdir = pathname(path)
    end
    def datadir
      @datadir || File.join(prefix, base_datadir)
    end
    def datadir=(path)
      @datadir = pathname(path)
    end
    def mandir
      @mandir || File.join(prefix,  base_mandir)
    end
    def mandir=(path)
      @mandir = pathname(path)
    end
    def docdir
      @docdir || File.join(prefix, base_docdir)
    end
    def docdir=(path)
      @docdir = pathname(path)
    end
    def rbdir
      @rbdir || File.join(prefix, base_rubylibdir)
    end
    def sodir
      @sodir || File.join(prefix, base_rubyarchdir)
    end
    def sysconfdir
      @sysconfdir ||= base_sysconfdir
    end
    def sysconfdir=(path)
      @sysconfdir = pathname(path)
    end
    def localstatedir
      @localstatedir ||= base_localstatedir
    end
    def localstatedir=(path)
      @localstatedir = pathname(path)
    end
    def rubypath
      @rubypath ||= File.join(RBCONFIG['bindir'], RBCONFIG['ruby_install_name'] + RBCONFIG['EXEEXT'])
    end
    def rubypath=(path)
      @rubypath = pathname(path)
    end
    def rubyprog
      @rubyprog || rubypath
    end
    def rubyprog=(command)
      @rubyprog = command
    end
    def makeprog
      @makeprog ||= (
        if arg = RBCONFIG['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
          arg.sub(/'/, '').split(/=/, 2)[1]
        else
          'make'
        end
      )
    end
    def makeprog=(command)
      @makeprog = command
    end
    def extconfopt
      @extconfopt ||= ''
    end
    def extconfopt=(string)
      @extconfopt = string
    end
    def shebang
      @shebang ||= 'ruby'
    end
    def shebang=(val)
      if %w(all ruby never).include?(val)
        @shebang = val
      else
        raise Error, "bad config: use SHEBANG=(all|ruby|never) [#{val}]"
      end
    end
    def no_ext
      @no_ext
    end
    def no_ext=(val)
      @no_ext = boolean(val)
    end
    def no_test
      @no_test
    end
    def no_test=(val)
      @no_test = boolean(val)
    end
    def no_doc
      @no_doc
    end
    def no_doc=(val)
      @no_doc = boolean(val)
    end
    def compile?
      !no_ext
    end
    def test?
      !no_test
    end
    def document?
      !no_doc
    end
    def to_h
      h = {}
      self.class.options.each do |name, type, description|
        h[name] = __send__(name)
      end
      h
    end
    def to_s
      to_yaml.sub(/\A---\s*\n/,'')
    end
    def to_yaml(*args)
      to_h.to_yaml(*args)
    end
    def save_config
      out = to_yaml
      if not File.exist?(File.dirname(CONFIGFILE))
        FileUtils.mkdir_p(File.dirname(CONFIGFILE))
      end
      if File.exist?(CONFIGFILE)
        txt = File.read(CONFIGFILE)
        return nil if txt == out
      end          
      File.open(CONFIGFILE, 'w'){ |f| f << out }
      true
    end
    def exist?
      File.exist?(CONFIGFILE)
    end
  private
    def pathname(path)
      path.gsub(%r<\\$([^/]+)>){ self[$1] }
    end
    def boolean(val, name=nil)
      case val
      when true, false, nil
        val
      else
        case val.to_s.downcase
        when 'y', 'yes', 't', 'true'
           true
        when 'n', 'no', 'f', 'false'
           false
        else
          raise Error, "bad config: use --#{name}=(yes|no) [\#{val}]"
        end
      end
    end
    def subprefix(path, with='')
      val = RBCONFIG[path]
      raise "Unknown path -- #{path}" if val.nil?
      prefix = Regexp.quote(RBCONFIG['prefix'])
      val.sub(/\A#{prefix}/, with)
    end
    def home
      ENV['HOME'] || raise(Error, 'HOME is not set.')
    end
  end #class ConfigTable
end #module Setup
=begin
    def inintialize_metaconfig
      path = Dir.glob(METACONFIG_FILE).first
      if path && File.file?(path)
        MetaConfigEnvironment.new(self).instance_eval(File.read(path), path)
      end
    end
    class MetaConfigEnvironment
      def initialize(config) #, installer)
        @config    = config
      end
      def config_names
        @config.descriptions.collect{ |n, t, d| n.to_s }
      end
      def config?(name)
        @config.descriptions.find do |sym, type, desc|
          sym.to_s == name.to_s
        end
      end
      def bool_config?(name)
        @config.descriptions.find do |sym, type, desc|
          sym.to_s == name.to_s && type == :bool
        end
      end
      def path_config?(name)
        @config.descriptions.find do |sym, type, desc|
          sym.to_s == name.to_s && type == :path
        end
      end
      def value_config?(name)
        @config.descriptions.find do |sym, type, desc|
          sym.to_s == name.to_s && type != :prog
        end
      end
      def add_config(name, default, desc)
        @config.descriptions << [name.to_sym, nil, desc]
      end
      def add_bool_config(name, default, desc)
        @config.descriptions << [name.to_sym, :bool, desc]
      end
      def add_path_config(name, default, desc)
        @config.descriptions << [name.to_sym, :path, desc]
      end
      def set_config_default(name, default)
        @config[name] = default
      end
      def remove_config(name)
        item = @config.descriptions.find do |sym, type, desc|
          sym.to_s == name.to_s
        end
        index = @config.descriptions.index(item)
        @config.descriptions.delete(index)
      end
    end
=end
module Setup
  class Documentor < Base
    def document
      return if config.no_doc
      exec_ri
    end
    def exec_ri
      case config.type #installdirs
      when 'std', 'ruby'
        output = "--ri-system"
      when 'site'
        output = "--ri-site"
      when 'home'
        output = "--ri"
      else
        abort "bad config: should not be possible -- type=#{config.type}"
      end
      if File.exist?('.document')
        files = File.read('.document').split("\n")
        files.reject!{ |l| l =~ /^\s*[#]/ || l !~ /\S/ }
        files.collect!{ |f| f.strip }
      else
        files = []
        files << 'lib' if File.directory?('lib')
        files << 'ext' if File.directory?('ext')
      end
      opt = []
      opt << "-U"
      opt << "-q" #if quiet?
      opt << output
      opt << files
      opt = opt.flatten
      cmd = "rdoc " + opt.join(' ')
      if trial?
        puts cmd
      else
        begin
          system(cmd)
          io.puts "Ok ri." #unless quiet?
        rescue Exception
          $stderr.puts "ri generation failed"
          $stderr.puts "command was: '#{cmd}'"
          $stderr.puts "proceeding with install..."
        end
      end
    end
    def exec_rdoc
      main = Dir.glob("README{,.*}", File::FNM_CASEFOLD).first
      if File.exist?('.document')
        files = File.read('.document').split("\n")
        files.reject!{ |l| l =~ /^\s*[#]/ || l !~ /\S/ }
        files.collect!{ |f| f.strip }
      else
        files = []
        files << main  if main
        files << 'lib' if File.directory?('lib')
        files << 'ext' if File.directory?('ext')
      end
      checkfiles = (files + files.map{ |f| Dir[File.join(f,'*','**')] }).flatten.uniq
      if FileUtils.uptodate?('doc/rdoc', checkfiles)
        puts "RDocs look current."
        return
      end
      output    = 'doc/rdoc'
      title     = (PACKAGE.capitalize + " API").strip if PACKAGE
      template  = config.doctemplate || 'html'
      opt = []
      opt << "-U"
      opt << "-q" #if quiet?
      opt << "--op=#{output}"
      opt << "--title=#{title}"
      opt << "--main=#{main}"     if main
      opt << files
      opt = opt.flatten
      cmd = "rdoc " + opt.join(' ')
      if trial?
        puts cmd 
      else
        begin
          system(cmd)
          puts "Ok rdoc." unless quiet?
        rescue Exception
          puts "Fail rdoc."
          puts "Command was: '#{cmd}'"
          puts "Proceeding with install anyway."
        end
      end
    end
  end
end
module Setup
  class Installer < Base
    def install_prefix
      config.install_prefix
    end
    def install
      Dir.chdir(rootdir) do
        install_bin
        install_ext
        install_lib
        install_data
        install_man
        install_doc
        install_etc
        prune_install_record
      end
    end
    def install_bin
      return unless directory?('bin')
      io.puts "* bin -> #{config.bindir}" unless quiet?
      files = files('bin')
      install_files('bin', files, config.bindir, 0755)
    end
    def install_ext
      return unless directory?('ext')
      io.puts "* ext -> #{config.sodir}" unless quiet?
      files = files('ext')
      files = select_dllext(files)
      files.each do |file|
        name = File.join(File.dirname(File.dirname(file)), File.basename(file))
        dest = destination(config.sodir, name)
        install_file('ext', file, dest, 0555, install_prefix)
      end
    end
    def install_lib
      return unless directory?('lib')
      io.puts "* lib -> #{config.rbdir}" unless quiet?
      files = files('lib')
      install_files('lib', files, config.rbdir, 0644)
    end
    def install_data
      return unless directory?('data')
      io.puts "* data -> #{config.datadir}" unless quiet?
      files = files('data')
      install_files('data', files, config.datadir, 0644)
    end
    def install_etc
      return unless directory?('etc')
      io.puts "* etc -> #{config.sysconfdir}" unless quiet?
      files = files('etc')
      install_files('etc', files, config.sysconfdir, 0644)
    end
    def install_man
      return unless directory?('man')
      io.puts "* man -> #{config.mandir}" unless quiet?
      files = files('man')
      install_files('man', files, config.mandir, 0644)
    end
    def install_doc
      return unless config.document?  # TODO: seprate ri generation from doc installation
      return unless directory?('doc')
      return unless project.name
      dir   = File.join(config.docdir, "ruby-{project.name}")
      io.puts "* doc -> #{dir}" unless quiet?
      files = files('doc')
      install_files('doc', files, dir, 0644)
    end
  private
    def directory?(path)
      File.directory?(path)
    end
    def files(dir)
      files = Dir["#{dir}/**/*"]
      files = files.select{ |f| File.file?(f) }
      files = files.map{ |f| f.sub("#{dir}/", '') }
      files
    end
    def select_dllext(files)
      ents = files.select do |file| 
        File.extname(file) == ".#{dllext}"
      end
      if ents.empty? && !files.empty?
        raise Error, "ruby extention not compiled: 'setup.rb setup' first"
      end
      ents
    end
    def dllext
      config.dlext
    end
    def install_files(dir, list, dest, mode)
      list.each do |fname|
        rdest = destination(dest, fname)
        install_file(dir, fname, rdest, mode, install_prefix)
      end
    end
    def install_file(dir, from, dest, mode, prefix=nil)
      mkdir_p(File.dirname(dest))
      if trace? or trial?
        io.puts "install #{dir}/#{from} #{dest}"
      end
      return if trial?
      str = binread(File.join(dir, from))
      if diff?(str, dest)
        trace_off {
          rm_f(dest) if File.exist?(dest)
        }
        File.open(dest, 'wb'){ |f| f.write(str) }
        File.chmod(mode, dest)
      end
      record_installation(dest) # record file as installed
    end
    def mkdir_p(dirname) #, prefix=nil)
      return if File.directory?(dirname)
      io.puts "mkdir -p #{dirname}" if trace? or trial?
      return if trial?
      dirs = File.expand_path(dirname).split(%r<(?=/)>)
      if /\A[a-z]:\z/i =~ dirs[0]
        disk = dirs.shift
        dirs[0] = disk + dirs[0]
      end
      dirs.each_index do |idx|
        path = dirs[0..idx].join('')
        unless File.dir?(path)
          Dir.mkdir(path)
        end
        record_installation(path)  # record directories made
      end
    end
    def record_installation(path)
      File.open(install_record, 'a') do |f|
        f.puts(path)
      end
    end
    def prune_install_record
      entries = File.read(install_record).split("\n")
      entries.uniq!
      File.open(install_record, 'w') do |f|
        f << entries.join("\n")
        f << "\n"
      end
    end
    def install_record
      @install_record ||= (
        file = INSTALL_RECORD
        dir  = File.dirname(file)
        unless File.directory?(dir)
          FileUtils.mkdir_p(dir)
        end
        file
      )
    end
    def destination(dir, file)
      dest = install_prefix ? File.join(install_prefix, File.expand_path(dir)) : dir
      dest = File.join(dest, file) #if File.dir?(dest)
      dest = File.expand_path(dest)
      dest
    end
    def diff?(new_content, path)
      return true unless File.exist?(path)
      new_content != binread(path)
    end
    def binread(fname)
      File.open(fname, 'rb') do |f|
        return f.read
      end
    end
    def install_shebang(files, dir)
      files.each do |file|
        path = File.join(dir, File.basename(file))
        update_shebang_line(path)
      end
    end
    def update_shebang_line(path)
      return if trial?
      return if config.shebang == 'never'
      old = Shebang.load(path)
      if old
        if old.args.size > 1
          $stderr.puts "warning: #{path}"
          $stderr.puts "Shebang line has too many args."
          $stderr.puts "It is not portable and your program may not work."
        end
        new = new_shebang(old)
        return if new.to_s == old.to_s
      else
        return unless config.shebang == 'all'
        new = Shebang.new(config.rubypath)
      end
      $stderr.puts "updating shebang: #{File.basename(path)}" if trace?
      open_atomic_writer(path) do |output|
        File.open(path, 'rb') do |f|
          f.gets if old   # discard
          output.puts new.to_s
          output.print f.read
        end
      end
    end
    def new_shebang(old)
      if /\Aruby/ =~ File.basename(old.cmd)
        Shebang.new(config.rubypath, old.args)
      elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
        Shebang.new(config.rubypath, old.args[1..-1])
      else
        return old unless config.shebang == 'all'
        Shebang.new(config.rubypath)
      end
    end
    def open_atomic_writer(path, &block)
      tmpfile = File.basename(path) + '.tmp'
      begin
        File.open(tmpfile, 'wb', &block)
        File.rename tmpfile, File.basename(path)
      ensure
        File.unlink tmpfile if File.exist?(tmpfile)
      end
    end
    class Shebang
      def Shebang.load(path)
        line = nil
        File.open(path) {|f|
          line = f.gets
        }
        return nil unless /\A#!/ =~ line
        parse(line)
      end
      def Shebang.parse(line)
        cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
        new(cmd, args)
      end
      def initialize(cmd, args = [])
        @cmd = cmd
        @args = args
      end
      attr_reader :cmd
      attr_reader :args
      def to_s
        "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
      end
    end
  end
end
module Setup
  class Tester < Base
    RUBYSCRIPT  = 'script/.setup/test.rb'
    SHELLSCRIPT = 'script/test'
    def testable?
      return false if config.no_test
      return true  if File.exist?(RUBYSCRIPT)
      return true  if File.exist?(SHELLSCRIPT)
      false
    end
    def test
      return if !testable?
      if File.exist?(RUBYSCRIPT)
        test_rubyscript
      elsif File.exist?(SHELLSCRIPT)
        test_shellscript
      end
    end
    def test_shellscript
      bash(SHELLSCRIPT)
    end
    def test_rubyscript
      ruby(RUBYSCRIPT)
    end
  end
end
module Setup
  class Uninstaller < Base
    def uninstall
      paths = File.read(INSTALL_RECORD).split("\n")
      dirs, files = paths.partition{ |f| File.dir?(f) }
      remove = []
      files.uniq.each do |file|
        next if /^\#/ =~ file  # skip comments
        remove << file if File.exist?(file)
      end
      if trace? && !trial?
        puts remove.collect{ |f| "rm #{f}" }.join("\n")
        ans = ask("Continue?", "yN")
        case ans
        when 'y', 'Y', 'yes'
        else
          return # abort?
        end
      end
      remove.each do |file|
        rm_f(file)
      end
      dirs.each do |dir|
        empty = Dir[File.join(dir,'*')].empty?
        begin
          if trial?
            io.puts "rmdir #{dir}"
          else
            rmdir(dir) if empty
          end
        rescue Errno::ENOTEMPTY
          io.puts "may not be empty -- #{dir}" if trace?
        end
      end
      rm_f(INSTALL_RECORD)
    end
  end
end
require 'optparse'
module Setup
  class Command
    def self.run(*argv)
      new.run(*argv)
    end
    def self.tasks
      @tasks ||= {}
    end
    def self.order
      @order ||= []
    end
    def self.task(name, description)
      tasks[name] = description
      order << name
    end
    task 'all'      , "do config, setup, then install"
    task 'config'   , "saves your configuration"
    task 'show'     , "show current configuration"
    task 'setup'    , "compile ruby extentions"
    task 'test'     , "run tests"
    task 'document' , "generate ri documentation"
    task 'install'  , "install project files"
    task 'uninstall', "uninstall previously installed files"
    task 'clean'    , "does `make clean' for each extention"
    task 'distclean', "does `make distclean' for each extention"
    def run(*argv)
      ARGV.replace(argv) unless argv.empty?
      task = ARGV.find{ |a| a !~ /^[-]/ }
      task = 'all' unless task
      unless task_names.include?(task)
        $stderr.puts "Not a valid task -- #{task}"
        exit 1
      end
      parser  = OptionParser.new
      options = {}
      parser.banner = "Usage: #{File.basename($0)} [TASK] [OPTIONS]"
      optparse_header(parser, options)
      case task
      when 'all'
        optparse_all(parser, options)
      when 'config'
        optparse_config(parser, options)
      when 'install'
        optparse_install(parser, options)
      end
      optparse_common(parser, options)
      begin
        parser.parse!(ARGV)
      rescue OptionParser::InvalidOption
        $stderr.puts $!.to_s.capitalize
        exit 1
      end
      rootdir = session.project.rootdir
      begin
        session.__send__(task)
      rescue Error
        raise if $DEBUG
        $stderr.puts $!.message
        $stderr.puts "Try 'setup.rb --help' for detailed usage."
        exit 1
      end
      puts unless session.quiet?
    end
    def session
      @session ||= Session.new(:io=>$stdout)
    end
    def configuration
      @configuration ||= session.configuration
    end
    def optparse_header(parser, options)
      parser.banner = "USAGE: #{File.basename($0)} [command] [options]"
    end
    def optparse_all(parser, options)
      optparse_config(parser, options)
    end
    def optparse_config(parser, options)
      parser.separator ""
      parser.separator "Configuration options:"
      configuration.options.each do |name, type, desc|
        optname = name.to_s.gsub('_', '-')
        case type
        when :bool
          if optname.index('no-') == 0
            optname = "[no-]" + optname.sub(/^no-/, '')
            parser.on("--#{optname}", desc) do |val|
              configuration.__send__("#{name}=", !val)
            end
          else
            optname = "[no-]" + optname.sub(/^no-/, '')
            parser.on("--#{optname}", desc) do |val|
              configuration.__send__("#{name}=", val)
            end
          end
        else
          parser.on("--#{optname} #{type.to_s.upcase}", desc) do |val|
            configuration.__send__("#{name}=", val)
          end
        end
      end
    end
    def optparse_install(parser, options)
      parser.separator ""
      parser.separator "Install options:"
      parser.on("--prefix PATH", "Installation prefix") do |val|
        configuration.install_prefix = val
      end
    end
    def optparse_uninstall(parser, options)
    end
    def optparse_common(parser, options)
      parser.separator ""
      parser.separator "General options:"
      parser.on("-q", "--quiet", "Suppress output") do |val|
        session.options[:quiet] = val
      end
      parser.on("--trace", "--verbose", "Watch execution") do |val|
        session.options[:trace] = true
      end
      parser.on("--trial", "--no-harm", "Do not write to disk") do |val|
        session.options[:trial] = true
      end
      parser.separator ""
      parser.separator "Inform options:"
      parser.on_tail("-h", "--help", "display this help information") do
        puts parser
        exit
      end
      parser.on_tail("--version", "-v", "Show version") do
        puts File.basename($0) + ' v' + Setup::VERSION #Version.join('.')
        exit
      end
      parser.on_tail("--copyright", "Show copyright") do
        puts Setup::COPYRIGHT #opyright
        exit
      end
    end
    def task_names
      self.class.tasks.keys
    end
=begin
    def help
    fmt = " " * 12 + "%-10s       %s"
      commands = self.class.order.collect do |k|
        d = self.class.tasks[k]
        (fmt % ["#{k}", d])
      end.join("\n").strip
      fmt = " " * 13 + "%-20s       %s"
      configs = configuration.options.collect do |k,t,d|
        (fmt % ["--#{k}", d])
      end.join("\n").strip
      text = <<-END
        USAGE: #{File.basename($0)} [command] [options]
        Commands:
        Options for CONFIG:
        Options for INSTALL:
            --prefix                      Set the install prefix
        Options in common:
            -q --quiet                    Silence output
               --verbose                  Provide verbose output
            -n --no-write                 Do not write to disk
      END
      text.gsub(/^\ {8,8}/, '')
    end
=end
  end
end
Setup::Command.run
